package org.deltaset.meta.internal;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.deltaset.meta.EntityResolver;
import org.deltaset.meta.MetaAction;
import org.deltaset.meta.MetaField;
import org.deltaset.meta.annotation.DsCustom;

import org.deltaset.meta.annotation.DsLabel;
import org.deltaset.meta.annotation.DsView;
import org.deltaset.meta.annotation.DsViewType;
import org.deltaset.meta.annotation.DsViews;
import org.deltaset.meta.extension.RepositoryProvider;

/**
 *
 * @author dmakariev
 */
class MetaClassFactory {

    static MetaClass forClass(final Class entityClass, final RepositoryProvider repositoryProvider) {
        final Class defaultRepositoryClass = repositoryProvider.defaultRepositoryClass();
        final EntityResolver entityResolver = repositoryProvider.createEntityResolver();
        final List<MetaField> metaFields = extractMetaFields(entityClass, entityResolver);
        final Map<DsViewType, List<MetaField>> orderedFieldsMap = orderedFieldsMap(entityClass, metaFields);
        final GroupFactory groupFactory = new GroupFactory(entityClass, defaultRepositoryClass, entityResolver);
        final MetaAction findByIdAction = groupFactory.getFindByIdAction();
        final MetaAction defaultFinder = groupFactory.getDefaultFinder();
        final Map<MetaActionGroupType, List<MetaAction>> actionMap = groupFactory.getActionGroupMap();
        final List<MetaAction> allMetaActions = groupFactory.getAllMetaActions();

        //return new MetaClass(entityClass, metaFields, orderedFieldsMap, metaActions);
        return new MetaClass(entityClass, orderedFieldsMap,metaFields, findByIdAction, defaultFinder, actionMap, allMetaActions);
    }

    static MetaParameter extractMetaParameter(final Method method, final Class executeClass, final EntityResolver entityResolver) {
        final Type[] genericParameterTypes = method.getGenericParameterTypes();
        final Class[] parameterTypes = method.getParameterTypes();
        final int parametersLength = parameterTypes.length;
        if (0 == parametersLength) {
            return null;
        }

        final boolean isPagedFinder = ReflectionUtils.isPagedFinder(method);
        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        final boolean areSupportedParameters = areSupportedParameters(parameterTypes, entityResolver);
        //TODO: review ? should be changed to support 'context service methods' - taking as parameter the model
        final boolean isJustOneParameter = (true == isPagedFinder) ? 3 == parametersLength : 1 == parametersLength;
        final Class probableScalarParameter = parameterTypes[parametersLength - 1];
        if (true == areSupportedParameters) {
            final int correction = (true == isPagedFinder) ? 2 : 0;
            final int withoutPagingLength = parametersLength - correction;

            final Class[] parameterTypesWithoutPaging = new Class[withoutPagingLength];//parameterTypes;
            final Type[] genericParameterTypesWithoutPaging = new Type[withoutPagingLength];
            final Annotation[][] parameterAnnotationsWithoutPaging = new Annotation[withoutPagingLength][];//parameterAnnotations;

            System.arraycopy(parameterTypes, correction, parameterTypesWithoutPaging, 0, withoutPagingLength);
            System.arraycopy(genericParameterTypes, correction, genericParameterTypesWithoutPaging, 0, withoutPagingLength);            
            System.arraycopy(parameterAnnotations, correction, parameterAnnotationsWithoutPaging, 0, withoutPagingLength);

            return metaParameterFromArray(parameterTypesWithoutPaging,genericParameterTypesWithoutPaging, parameterAnnotationsWithoutPaging, entityResolver);
        } else if (true == isJustOneParameter && ReflectionUtils.hasDefaultConstructor(probableScalarParameter)) {
            return metaParameterFromClass(probableScalarParameter, entityResolver);
        }

        return null;
    }

    private static MetaParameter metaParameterFromArray(final Class[] parameterTypes,final Type[] genericParameterTypes, final Annotation[][] parameterAnnotations, final EntityResolver entityResolver) {
        final List<MetaField> metaFields = extractMetaFields(parameterTypes,genericParameterTypes, parameterAnnotations, entityResolver);
        return new MetaParameter(metaFields, null, parameterTypes);
    }

    private static MetaParameter metaParameterFromClass(final Class scalarClass, final EntityResolver entityResolver) {
        final List<MetaField> metaFields = extractMetaFields(scalarClass, entityResolver);
        return new MetaParameter(metaFields, scalarClass, new Class[]{scalarClass});
    }

    private static boolean areSupportedParameters(final Class[] parameterTypes, final EntityResolver entityResolver) {
        boolean result = true;
        for (Class parameterClass : parameterTypes) {
            final boolean isEntity = entityResolver.isEntity(parameterClass);
            result = result && ReflectionUtils.isSupportedParameterClass(parameterClass, isEntity);
        }
        return result;
    }

    private static Map<DsViewType, List<MetaField>> orderedFieldsMap(final Class currentClass, final List<MetaField> fields) {
        final Map<DsViewType, List<MetaField>> orderedFieldsMap = new EnumMap<DsViewType, List<MetaField>>(DsViewType.class);

        for (DsViewType dsViewType : DsViewType.values()) {
            final List<MetaField> ordered = orderedFieldsFor(dsViewType, currentClass, fields);
            orderedFieldsMap.put(dsViewType, ordered);
        }
        return orderedFieldsMap;
    }

    //creating the ordered fields for a 'view' (list, detail, form)
    private static List<MetaField> orderedFieldsFor(final DsViewType type, final Class currentClass, final List<MetaField> fields) {

        final DsViews views = (DsViews) currentClass.getAnnotation(DsViews.class);
        if (null != views) {
            for (final DsView dView : views.value()) {
                if (dView.type().equals(type)) {
                    final List<MetaField> ordered = new ArrayList<MetaField>();
                    for (final String propertyName : dView.order()) {
                        for (final MetaField metaField : fields) {
                            if (metaField.getPropertyName().equals(propertyName)) {
                                ordered.add(metaField);
                            }
                        }
                    }
                    return ordered;
                }
            }
        }
        return new ArrayList<MetaField>(fields);
    }

    private static List<MetaField> extractMetaFields(final Class[] parameterTypes,final Type[] genericParameterTypes, final Annotation[][] parameterAnnotations, final EntityResolver entityResolver) {
        final List<MetaField> metaFields = new ArrayList<MetaField>();
        for (int i = 0; i < parameterTypes.length; i++) {
            final Annotation[] annotations = parameterAnnotations[i];
            final Class parameterClass = parameterTypes[i];
            final Type genericParameterType = genericParameterTypes[i];
            final String fieldName = MetaFieldImpl.fieldName(i);
            
            final BeanProperty beanProperty = new BeanProperty(fieldName, parameterClass,genericParameterType, annotations);
            
            final MetaFieldImpl metaField = convertToMetaField( beanProperty, entityResolver);
            metaFields.add(metaField);
        }
        return metaFields;
    }        

    private static List<MetaField> extractMetaFields(final Class currentClass, final EntityResolver entityResolver) {
        final List<MetaField> metaFields = new ArrayList<MetaField>();
        final List<BeanProperty> beanProperties = ReflectionUtils.findBeanProperties(currentClass);
       
        for (final BeanProperty beanProperty : beanProperties) {         
            final MetaFieldImpl metaField = convertToMetaField(beanProperty, entityResolver);
            metaFields.add(metaField);
        }
        
        return metaFields;
    }
   

    private static MetaFieldImpl convertToMetaField(final BeanProperty beanProperty, final EntityResolver entityResolver) {             
        final String fieldLabelKey = extractLabel(beanProperty);
        final String propertyType = extractPropertyType(beanProperty, entityResolver);
        final Map<String, String> params = extractParams(beanProperty);
        final MetaFieldImpl metaField = new MetaFieldImpl(beanProperty, fieldLabelKey, propertyType, params);
        return metaField;
    }

    private static Map<String, String> extractParams(final BeanProperty beanProperty) {
        final Annotation[] annotations = beanProperty.getFieldAnnotations();
        //accepts value pairs separated by '='
        final Map<String, String> paramMap = new HashMap<String, String>();
        for (final Annotation annotation : annotations) {
            if (annotation.annotationType().equals(DsCustom.class)) {
                final String[] params = ((DsCustom) annotation).params();
                for (final String param : params) {
                    final String[] pair = param.split("=");
                    if (pair.length != 2) {
                        throw new IllegalArgumentException("@DsCustom supports params interface a format 'key=value'");
                    }
                    final String key = pair[0];
                    final String value = pair[1];
                    paramMap.put(key, value);
                }
            }
        }
        return paramMap;
    }

    private static String extractPropertyType(final BeanProperty beanProperty, final EntityResolver entityResolver) {
        final Annotation[] annotations = beanProperty.getFieldAnnotations();
        final MetaFieldType metaFieldType = MetaFieldType.mapField(beanProperty, entityResolver);
        final String propertyType = metaFieldType.toString().toLowerCase();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(DsCustom.class)) {
                final String customType = ((DsCustom) annotation).value();               
                return ("".equals(customType))?propertyType:customType;
            }
        }
        return propertyType;
    }

    private static String extractLabel(final BeanProperty beanProperty) {
        final String fieldName = beanProperty.getFieldName();
        final Annotation[] annotations = beanProperty.getFieldAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(DsLabel.class)) {
                final DsLabel dsLabel = (DsLabel) annotation;
                final String fieldLabel = dsLabel.value();
                return fieldLabel;
            }
        }
        return fieldName;
    }  
}
